使用模拟器进行x64驱动的Safengine脱壳+导入表修复
0x00 概述
近日闲的蛋疼发现一款某地外挂被火绒爆勒索,本来准备安排一哈它的勒索功能。
结果发现sys直接明文存放在exe里,我也懒得花钱,遂dump之。
IDA看了下发现是Safengine Shielden v2.4.0.0。
0x02 分析
直接加载sys后发现往afd.sys挂了一个注册表回调,回调入口是跳板。
由于驱动直接加载会返回C0000001导致立刻被卸载,无法直接dump。
于是改用模拟器加载并在真实DriverEntry处dump整个sys内存:
经过调试发现Safengine保护的驱动会使用DriverObject->DriverSection进行PsLoadedModuleList的遍历并修复导入表。
如果在模拟器中给上假的DriverSection会导致SE访问到非法内存。
于是我们给模拟器补上自己伪造的PsLoadedModuleList和DriverSection:
DriverObject.DriverSection = (PVOID)m_DriverLdrEntry;
补完后可以正常执行到真实入口点,可以看到这个sys执行的第一次API是RtlInitUnicodeString:
我们定义代码从加壳sys的.sedata/.vmp节执行到.text 或INIT 就算进入真实入口点:
bool bIsUnknownSection = (0 == memcmp((char *)SectionHeader[i].Name, ".text\0\0\0", 8)
|| 0 == memcmp((char *)SectionHeader[i].Name, "INIT\0\0\0\0", 8)) ? false : true;
if(currentModule == ctx->m_ImageBase && ctx->m_IsPacked && !ctx->m_ImageRealEntry)
{
FakeSection_t *section = NULL;
if (ctx->FindSectionByAddress(address, §ion) && !section->IsUnknownSection)
{
ctx->m_ImageRealEntry = address;
}
}
至此,我们可以完整dump出刚刚进入真实入口点时的加壳驱动。
virtual_buffer_t imagebuf(ctx.m_ImageEnd - ctx.m_ImageBase);
uc_mem_read(uc, ctx.m_ImageBase, imagebuf.GetBuffer(), ctx.m_ImageEnd - ctx.m_ImageBase);
FILE *fp = fopen("dump.sys", "wb");
fwrite(imagebuf.GetBuffer(), ctx.m_ImageEnd - ctx.m_ImageBase, 1, fp);
fclose(fp);
对比加壳sys和dumpsys可以发现 safengine使用了代码自修改。
从区段自带可写入属性就可以看出这一点。
当然,直接dump的sys是没法脱IDA的,还需要修正一下节表(文件和内存中的RVA完全一致)。
auto SectionCount = ntheader->FileHeader.NumberOfSections;
for (USHORT i = 0; i < SectionCount; ++i)
{
SectionHeader[i].PointerToRawData = SectionHeader[i].VirtualAddress;
SectionHeader[i].SizeOfRawData = SectionHeader[i].Misc.VirtualSize;
}
以及修正入口点RVA到我们刚才用模拟器跑出的真正入口点。
OptionalHeader.AddressOfEntryPoint = (ULONG)(ctx.m_ImageRealEntry - ctx.m_ImageBase);
这样入口点就能被IDA正确识别了。
然后还需要修复导入表,由于没法用工具修复,我们只能自己手撸代码解决。
在可执行节中搜索所有的FF15 FF25(call qword ptr[xxx]和jmp qword ptr [xxx])。
using namespace blackbone;
PatternSearch patternFF15("\xFF\x15");
PatternSearch patternFF25("\xFF\x25");
auto SectionCount = ntheader->FileHeader.NumberOfSections;
for (USHORT i = 0; i < SectionCount; ++i)
{
if (SectionHeader[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE))
{
patternFF15.Search((PUCHAR)ImageBase + SectionHeader[i].VirtualAddress,
SectionHeader[i].Misc.VirtualSize, out);
patternFF25.Search((PUCHAR)ImageBase + SectionHeader[i].VirtualAddress,
SectionHeader[i].Misc.VirtualSize, out);
}
}
对搜索到的所有 FF 15 xx xx xx xx 和FF 25 xx xx xx xx 判断后四字节是否是真正的RVA,并且根据RVA算出api地址,看api地址是否是有效API。
for (size_t j = 0; j < out.size(); ++j)
{
int insn_rva = (int)(out[j] - (ptr_t)ImageBase);
int call_rva = insn_rva + *(int *)(out[j] + 2) + 6;
if (call_rva >= 0 && call_rva < (int)ImageSize)
{
auto call_api_addr = *(ULONG_PTR *)(call_rva + (PUCHAR)ImageBase);
FakeAPI_t *api_ptr = NULL;
if (FindAPIByAddress(call_api_addr, dllnamew, &api_ptr))
{
std::cout << "found IAT 0x" << std::hex << insn_rva << " to " << api_ptr->ProcedureName << "\n";
//....
保存这些insn_rva和API名,利用这些数据重建导入表,新加一个.idata节存放导入表。
memset(&SectionHeader[SectionCount], 0, sizeof(IMAGE_SECTION_HEADER));
SectionHeader[SectionCount].VirtualAddress = SectionHeader[SectionCount].PointerToRawData = NewIATRva;
SectionHeader[SectionCount].SizeOfRawData = (DWORD)RebuildIATSize;
SectionHeader[SectionCount].Misc.VirtualSize = PAGE_ALIGN(RebuildIATSize);
SectionHeader[SectionCount].Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE;
memcpy(SectionHeader[SectionCount].Name, ".idata\0\0", 8);
ntheader->FileHeader.NumberOfSections++;
ntheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = RebuildIATRva;
ntheader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = (DWORD)RebuildIATDescSize;
ntheader->OptionalHeader.SizeOfImage += (DWORD)PAGE_ALIGN(RebuildIATSize);
fwrite(RebuildIATBuffer.GetBuffer(), RebuildIATBuffer.GetLength(), 1, fp);
并将新增的节追加到dump的PE文件尾部,修改导入表入口,成功重建部分导入表。
至此,该驱动的Safengine脱壳+导入表修复完美完成。
0x03 总结
用Safengine加壳驱动并且不vm不混淆任何函数是非常不安全的行为,在模拟器的安排下几乎等同于裸奔。(此时有效的保护只有代码重组,只能干扰一下IDA的F5,如果单看汇编就是裸奔)
用VMProtect 3.X加壳并保护导入表会稍微好一点,不过稍微修改一下本文中的导入表重建算法,像https://bbs.pediy.com/thread-248812.htm一样 识别出VMP的API call,也可以无压力完美脱壳。
样本和脱完壳的sys下载地址已放附件。(点击阅读原文即可获取)
0x04 相关项目
模拟器:
Unicorn PE (https://github.com/hzqst/unicorn_pe)
- End -
看雪ID:hzqst
https://bbs.pediy.com/user-619065.htm
本文由看雪论坛 hzqst 原创
转载请注明来自看雪社区
热门图书推荐:
征题正在火热进行中!
(晋级赛Q1即将于3月10日开启,敬请期待!)
热门文章阅读
热门课程推荐
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com